package cz.proks.easyhelpdesk.gae.api.impl.db;

import java.util.LinkedList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.beans.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;

import cz.proks.easyhelpdesk.api.BaseApi;
import cz.proks.easyhelpdesk.api.exception.ApiException;
import cz.proks.easyhelpdesk.api.model.base.AnIdentifiableModel;
import cz.proks.easyhelpdesk.gae.api.impl.dbo.base.ConvertibleDbo;

public abstract class BaseApiImpl<Entity extends AnIdentifiableModel>
		implements BaseApi<Entity> {
	private final Logger log = LoggerFactory.getLogger(getClass());
	
	private final static String DBO_PACKAGE = "cz.proks.easyhelpdesk.gae.api.impl.dbo";
	
	@PersistenceContext
	private EntityManager entityManager;
		
	protected abstract Class<Entity> getDomainClass();
	protected abstract Entity getDboObject(Entity entity);
	
	protected EntityManager getEntityManager() {
        return entityManager;
    }
	
	@Override
	@Transactional(readOnly = true)
	public Entity get(Long id) throws ApiException {
		log.debug("get():: Start with id '{}'", id);

		Entity entity = null;		
		ConvertibleDbo<? extends Entity> convertibleDbo = null;
		
		try { convertibleDbo = getEntityManager().find(getDboClass(), id); }
		catch (Exception e) {
			throw new ApiException(e);
		}

		entity = convertibleDbo == null ? null : convertibleDbo.asDomain(); 
		
		log.debug("get():: End with entity '{}'", entity);
		return entity;
	}

	@Override
	@Transactional(readOnly = false)
	public Long create(Entity entity) throws ApiException {
		log.debug("create():: Start with entity '{}'", entity);

		Entity dbo = getDboObject(entity);
		
		try { getEntityManager().persist(dbo); }
		catch(Exception e) {
			throw new ApiException(e);
		}
		
		Long id = dbo != null ? dbo.getId() : null;
		
		BeanUtils.copyProperties(dbo, entity);		
		
		log.debug("create():: End with id '{}'", id);
		return id;
	}

	@Override
	@Transactional(readOnly = false)
	public void update(Entity entity) throws ApiException {
		log.debug("update():: Start with entity '{}'", entity);
		
		Entity dbo = getDboObject(entity);
		
		try { dbo = getEntityManager().merge(dbo); }
		catch(Exception e) {
			throw new ApiException(e);
		}
		
        BeanUtils.copyProperties(dbo, entity);
        
		log.debug("update():: End");		
	}
	
	@Override
	@Transactional(readOnly = false)
	public void remove(Long id) throws ApiException {
		log.debug("delete():: Start with id '{}'", id);
		
		EntityManager entityManager = getEntityManager();
		
		ConvertibleDbo<? extends Entity> dbo = entityManager.find(getDboClass(), id);
		
		if (dbo != null)
			try { getEntityManager().remove(dbo); }
			catch(Exception e) {
				throw new ApiException(e);
			}

		log.debug("delete():: End");		
	}
	
	@SuppressWarnings("unchecked")
    protected <DboClass extends ConvertibleDbo<Entity>> Class<DboClass> getDboClass() {
	    Class<Entity> domainClass = getDomainClass();
	    try {
	        return (Class<DboClass>) Class.forName(DBO_PACKAGE + "." + domainClass.getSimpleName());
	    } catch (Exception e) {
            throw new RuntimeException(e);
        }   
	}
	
	protected <Dbo extends ConvertibleDbo<Entity>> Dbo instantiateDbo(Entity entity) {
        if (entity == null)
            return null;
        
        Class<Dbo> dboClass = getDboClass();
        Dbo dboObject = instantiate(dboClass);
        return dboObject;
    }
	
	protected <T> T instantiate(Class<T> clazz) {
	    try {
	        return clazz.newInstance();
	    } catch (Exception e) {
	        log.error("", e);
	        return null;
	    }
	}
	
	protected List<Entity> fromDboList(List<? extends ConvertibleDbo<Entity>> dboEntities) {
	    List<Entity> entities = new LinkedList<Entity>();
	    
	    if (dboEntities == null)
	        return entities;
	    	    
	    for (ConvertibleDbo<? extends Entity> dbo : dboEntities) {
	        entities.add(dbo.asDomain());
	    }
	    
	    return entities;
	}
	
	protected List<ConvertibleDbo<Entity>> toDboList(List<Entity> entities) {
	    List<ConvertibleDbo<Entity>> dboList = new LinkedList<ConvertibleDbo<Entity>>();
	    
	    if (entities == null)
	        return dboList;
	    	    
	    for (Entity entity : entities) {
	        ConvertibleDbo<Entity> dbo = instantiateDbo(entity);
	        dbo.toDbo(entity);
	        dboList.add(dbo);
	    }
	    
	    return dboList;
	}
	
	protected Entity fromDbo(ConvertibleDbo<Entity> dbo) {
	    return dbo != null ? dbo.asDomain() : null;
	}
	
	
	protected ConvertibleDbo<Entity> toDbo(Entity entity) {
	    ConvertibleDbo<Entity> dbo = instantiateDbo(entity);
	    dbo.toDbo(entity);
	    return dbo;
	}
}
